package org.fei.ClassDiagram;

import java.lang.reflect.InvocationTargetException;
import org.fei.ClassDiagram.WindowComponents.ClassDiagramView;
import org.fei.ClassDiagram.WindowComponents.Diagram;
import org.fei.ClassDiagram.WindowComponents.Message;
import org.fei.ClassDiagram.WindowComponents.MessageJPanel;
import org.fei.ClassDiagram.WindowComponents.PackageDiagram;
import org.fei.ClassDiagram.Layout.HierarchicalLayout;
import org.fei.ClassDiagram.Layout.SimplePackageLayout;
import org.fei.ClassDiagram.Layout.WrappedClass;
import java.net.MalformedURLException;
import org.fei.ClassDiagram.data.*;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import org.netbeans.modules.classfile.ClassFile;
//import org.netbeans.modules.classfile.ClassName;
import org.netbeans.modules.classfile.Method;
import org.netbeans.modules.classfile.Variable;
/*
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.net.URLClassLoader;
 */
import java.net.URL;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import javax.swing.SwingUtilities;
import org.fei.ClassDiagram.drawing.Classes.AbstractClassDrawing;
import org.fei.ClassDiagram.drawing.Lines.AsotiationArrow;
import org.fei.ClassDiagram.drawing.Classes.ClassDrawing;
import org.fei.ClassDiagram.drawing.Classes.Drawing;
import org.fei.ClassDiagram.drawing.Classes.EnumDrawing;
import org.fei.ClassDiagram.drawing.Lines.InheritanceArrow;
import org.fei.ClassDiagram.drawing.Lines.InnerClassArrow;
import org.fei.ClassDiagram.drawing.Lines.InterfaceArrow;
import org.fei.ClassDiagram.drawing.Classes.InterfaceDrawing;
import org.fei.ClassDiagram.drawing.Lines.Line;
import org.fei.ClassDiagram.drawing.Package.PackageDrawing;
import org.netbeans.modules.classfile.ClassName;
import org.netbeans.modules.classfile.InnerClass;
import org.openide.util.Exceptions;

/**
 *
 * @author Tomas
 */
public class ClassFileScanner {

    private ArrayList<String> classFiles;
    private String classDirPath;

    public ClassFileScanner(ArrayList<String> classesPath, String classDirPath, String projectName) {

        this.classFiles = classesPath;
        this.classDirPath = classDirPath;
        //Message.showMessage(classDirPath);
        ArrayList<Drawing> classes = new ArrayList<Drawing>();
        ArrayList<Line> lines = new ArrayList<Line>();
        
        ArrayList<PackageDrawing> packages = new ArrayList<PackageDrawing>();

        Map<Drawing, WrappedClass> wrappedClasses = new HashMap<Drawing, WrappedClass>();

        try {
            
            URL classDirUrl = new URL("file:///" + classDirPath + "/");
            URL[] urls = {classDirUrl};
            URLClassLoader csl = new URLClassLoader(urls);
            
            for (String s : this.classFiles) {
                ClassFile classFile = new ClassFile(s);       

                Collection<Method> methods = classFile.getMethods();
                Collection<Variable> variables = classFile.getVariables();

                ArrayList<MethodLook> mDatas = new ArrayList<MethodLook>();
                /*String[] classNames = classFile.getName().toString().split(".");
                String className = classNames[classNames.length-1];*/

                for (Method m : methods) {
                    if (classFile.isEnum()) {
                        if (!(m.getName().contains("<init>") || m.getName().contains("<clinit>"))) {
                            mDatas.add(new MethodLook(m, classFile));
                        }
                    } else {
                        // ak je staticke a je konstruktor tak sa neprida
                        if (!(m.isStatic() && (m.getName().contains("<init>") || m.getName().contains("<clinit>"))))
                        {
                            if (classFile.getInnerClasses().isEmpty()) {
                                mDatas.add(new MethodLook(m, classFile));
                            }
                            else if (m.isStatic() && m.getName().startsWith("access$")) { // pristupove metody pre vnorene triedy sa nepridaju
                                if (!m.getParameters().isEmpty()) {
                                    if (m.getParameters().get(0).getName().equals("x0") && classFile.getName().toString().endsWith(m.getParameters().get(0).getDeclaration().split(" ")[0])) {
                                        continue; // pristupove metody pre vnorene triedy sa nepridaju
                                    }
                                }
                                else {
                                    mDatas.add(new MethodLook(m, classFile));
                                }
                            }
                            else { //if (!(m.isStatic() && m.getName().startsWith("access$") && m.getParameters().get(0).getName().equals("x0") && classFile.getName().toString().endsWith(m.getParameters().get(0).getDeclaration().split(" ")[0])))
                                mDatas.add(new MethodLook(m, classFile));
                            }
                        }
                    }
                }

                ArrayList<VariableLook> vDatas = new ArrayList<VariableLook>();
                ArrayList<VariableLook> enumConstants = new ArrayList<VariableLook>();
                for (Variable v : variables) {
                    if (v.isEnumConstant()) {
                        enumConstants.add(new VariableLook(v));
                    }
                    if (!v.getName().startsWith("this$0")) {
                        vDatas.add(new VariableLook(v));
                    }
                }

                Drawing d;
                if (Modifier.isInterface(classFile.getAccess())) {
                    d = new InterfaceDrawing(0, 0, classFile, mDatas, vDatas);
                } else if (Modifier.isAbstract(classFile.getAccess())) {
                    d = new AbstractClassDrawing(0, 0, classFile, mDatas, vDatas);
                } else if (classFile.isEnum()) {
                    d = new EnumDrawing(0, 0, classFile, mDatas, enumConstants);
                } else {
                    d = new ClassDrawing(0, 0, classFile, mDatas, vDatas);
                }
                
                classes.add(d);
                wrappedClasses.put(d, new WrappedClass(d));
                
                // pridanie balikov
                String packageStr = classFile.getName().getPackage();
                String className = classFile.getName().getSimpleName();
                boolean packageExist = false;
                /*if (packages.isEmpty()) {
                    PackageDrawing pd = new PackageDrawing(10, 10, packageStr);
                    pd.addClass(className);
                    packages.add(pd);
                }
                else {*/
                    for (PackageDrawing pd : packages) {
                        if (pd.getName().equals(packageStr)) {
                            pd.addClass(className);
                            packageExist = true;
                        }
                    }
                    if (packageExist == false) {
                        PackageDrawing pd = new PackageDrawing(10, 10, packageStr);
                        pd.addClass(className);
                        packages.add(pd);                            
                    }
                    
                //}

                }

            // vytvorenie ciar pre dedicnost
            for (Drawing child : classes) {
                for (Drawing parent : classes) {
                    if (!parent.equals(child)) {
                        // dedicnost
                        if (child.getClassFile().getSuperClass().equals(parent.getClassFile().getName())) {
                            //Message.showMessage("potomok superclass: "+child.getClassFile().getSuperClass()+"rodic: "+parent.getClassFile().getName());
                            parent.setIsSuperclass(true);
                            lines.add(new InheritanceArrow(child.getTopPoint(), parent.getBottomPoint()));
                            //break;

                            // pridanie childov do wrappedclass
                            WrappedClass wrapParent = wrappedClasses.get(parent);
                            WrappedClass wrapChild = wrappedClasses.get(child);
                            wrapParent.addChild(wrapChild);
                            wrapChild.setParent(parent);
                        }
                        // implementacia rozhrani
                        for (ClassName interfaceClass : child.getClassFile().getInterfaces()) {
                            if (interfaceClass.equals(parent.getClassFile().getName())) {
                                parent.setIsSuperclass(true);
                                lines.add(new InterfaceArrow(child.getTopPoint(), parent.getBottomPoint()));


                                // pridanie childov do wrappedclass
                                WrappedClass wrapParent = wrappedClasses.get(parent);
                                WrappedClass wrapChild = wrappedClasses.get(child);
                                wrapParent.addChild(wrapChild);
                                if (!wrapChild.isParentSet()) {
                                    wrapChild.setParent(parent);
                                }
                            }
                        }
                        // vnorene triedy
                        for (InnerClass innerClass : child.getClassFile().getInnerClasses()) {
                            if (innerClass.getName().equals(parent.getClassFile().getName())) {
                                child.setHasInnerClasses(true);
                                lines.add(new InnerClassArrow(parent.getLeftPoint(), child.getInnerClassesPoint()));
                            }
                        }
                        // asociacie
                        for (VariableLook vl : child.getVariables()) {
                            //Message.showMessage(v.getDescriptor() + "\n" + v.getName() + "\n" + v.getTypeSignature() + "\n" + v.toString() );
                            Variable v = vl.getVar();
                            String type = v.getDescriptor();
                            if (type.contains("L") && type.contains(";")) {
                                type = type.substring(type.indexOf("L") + 1, type.indexOf(";"));
                            }
                            type = type.replace("/", ".");
                            //Message.showMessage(type);

                            if (type.equals(parent.getClassFile().getName().toString()) && !v.getName().startsWith("this$0")) {

                                parent.setIsAsotiationClass(true);
                                parent.setNumberOfAsotiation(parent.getNumberOfAsotiation() + 1);

                                boolean isFirst = true;
                                for (Line l : lines) {
                                    if (l instanceof AsotiationArrow) {
                                        if (l.getStart().equals(child.getLeftPoint()) && l.getEnd().equals(parent.getRightPoint())) {
                                            isFirst = false;
                                        }
                                    }
                                }
                                // koli vykreslovaniu v triedach
                                vl.setIsAsotiation(true);
                                lines.add(new AsotiationArrow(child.getLeftPoint(), parent.getRightPoint(), v, parent.getNumberOfAsotiation(), isFirst));
                            }
                            // takuto premennu maju v sebe vnorene triedy, nechcem tuto premennu vykreslovat
                            if (v.getName().startsWith("this$0")) {
                                vl.setIsAsotiation(true);
                            }
                        }
                    }
                }
            }
        } catch (IOException ex) {
            Message.showMessage("ClassFileScanner.java: " + ex.toString());
        } 

        if (classes.isEmpty()) {
            String[] msg = {"Your project does not contains any classes."};
            new ClassDiagramView(new MessageJPanel(msg), projectName);
            return;
        }
         
        // Layout diagramov tried
        final Diagram diagram = new Diagram(classes, lines);
        // robim koli ziskaniu sirky a vysky vykreslovanych tried
        BufferedImage bi = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
        Graphics g = bi.createGraphics();
        diagram.paint(g);

        for (WrappedClass w : wrappedClasses.values()) {
            w.updateWidthHeight();
        }
        HierarchicalLayout hl = new HierarchicalLayout(wrappedClasses.values());
        diagram.setDiagramSize(hl.getDiagramWidth(), hl.getDiagramHeight());
        //Message.showMessage("Width: " + hl.getDiagramWidth() + "\nHeight: " +hl.getDiagramHeight());

        // Layout diagramov balikov
        final PackageDiagram packageDiagram = new PackageDiagram(packages);
        packageDiagram.paint(g);
        
        SimplePackageLayout l = new SimplePackageLayout(packages);
        packageDiagram.setDiagramSize(l.getWidth(), l.getHeight());
        g.dispose();
        
        final String name = projectName;
        
        try {
            SwingUtilities.invokeAndWait(new Runnable() {

                @Override
                public void run() {
                    ClassDiagramView view = new ClassDiagramView(diagram, packageDiagram, name);
                }
                
            });
        } catch (InterruptedException ex) {
            Message.showMessage("ClassFileScanner.java: " + ex.toString());
        } catch (InvocationTargetException ex) {
            Message.showMessage("ClassFileScanner.java: " + ex.toString());
        }

        
    }
}